#!/bin/sh
. /etc/utils.sh

trap ": >/tmp/run/network.init" EXIT

register_experiment BigInitRwnd
register_experiment NoSTP
register_experiment TVBoxNoGigEthernet
register_experiment TcpWmem1Meg
register_experiment AggressiveTCP
register_experiment BigSocketBuffers
register_experiment MocaFlowControl


irq_affinity() {
  local irq="$1" cpu="$2"
  if [ -e "/proc/irq/$irq/smp_affinity" ]; then
    if ! echo "$cpu" >"/proc/irq/$irq/smp_affinity"; then
      echo "Failed to set affinity for IRQ $irq @ CPU $cpu"
    fi
  else
    echo "  ...irq #$irq not present, skipping" >&2
  fi
}


redistribute_irqs() {
  # if a second CPU isn't available, do nothing.
  echo "distribute IRQ handling between cores"
  if ! taskset 2 true 2>/dev/null; then
    echo "  ...only one CPU available, skipped."
  fi

  # Rule of thumb:
  #  - interrupts are generally by default on first CPU (bitfield=1)
  #  - let's move realtime sensitive stuff to second CPU (bitfield=2)
  #  - non-realtime userspace processes can default to both (bitfield=3)
  if is-storage-box || is-network-box; then
    # Storage box serves MoCA and ethernet simultaneously.
    # - put them both on separate CPUs to maximize throughput.
    # - consider ethernet (not MoCA) to be realtime sensitive, since it
    #   carries the multicast feed.
    # - consider disk to be non-realtime, since it can prefetch/writeback.
    serialcpu=1
    eth0cpu=2
    mocacpu=1
    wificpu=1  # may not actually have wifi
    diskcpu=1
  else
    # TV box handles video interrupts (via miniclient) and
    # (ethernet *or* MoCA) traffic (which carries the video stream), and wifi.
    # Network traffic includes the video stream, but it's TCP, so it should
    # be able to retransmit if packets are lost (unlike multicast on the
    # storage box) so it should be okay to put it on the non-realtime CPU.
    serialcpu=1
    eth0cpu=1
    mocacpu=1
    wificpu=1
    diskcpu=1  # doesn't actually have a disk
  fi

  if grep -q BCM7425 /proc/cpuinfo; then
    irq_affinity 62 $serialcpu
    irq_affinity 18 $eth0cpu
    irq_affinity 19 $eth0cpu
    irq_affinity 20 $mocacpu
    irq_affinity 21 $mocacpu
    irq_affinity 31 $mocacpu
    irq_affinity 34 $wificpu  # bcm wifi, if present
    irq_affinity 42 $diskcpu
  elif grep -q BCM7429 /proc/cpuinfo; then
    irq_affinity 52 $serialcpu
    irq_affinity 16 $eth0cpu
    irq_affinity 17 $eth0cpu
    irq_affinity 18 $mocacpu
    irq_affinity 19 $mocacpu
    irq_affinity 28 $mocacpu
    # Wifi is on one of mmc0 or mmc1, but it has varied between early
    # device models.  Since mmc is pretty fast and seldom used (and we happen
    # to want it on the non-realtime cpu anyway, like wifi), let's just
    # blindly put them both on the wifi cpu.
    irq_affinity 35 $wificpu  # mmc1
    irq_affinity 36 $wificpu  # mmc0
    # Note: no disk present, ignore $diskcpu
  elif grep -q comcerto /proc/iomem; then
    # TODO(apenwarr): we steer *all* PFE network traffic to $eth0cpu.
    #  Ideally we want only multicast to go there, and other traffic to
    #  go to the non-realtime CPU.  There may be some way to do this by
    #  changing PFE somehow.  As is, incoming WAN traffic destined for wifi
    #  could end up sucking all available CPU and interfere with multicast.
    irq_affinity  59 $serialcpu  # serial
    irq_affinity  62 $serialcpu  # i2c (NOR flash)
    irq_affinity  36 $eth0cpu    # *all* PFE
    irq_affinity  77 $mocacpu
    irq_affinity 128 $wificpu    # new boards, ath10k
    irq_affinity 160 $wificpu    # new boards, ath9k
    irq_affinity  96 $wificpu  2>/dev/null  # old boards, ath10k
    irq_affinity 164 $wificpu  2>/dev/null  # old boards, ath9k
    irq_affinity  48 $diskcpu
  elif grep -q LS1024A /proc/cpuinfo; then
    # Optimus with Linux 4.1
    irq_affinity  24 $serialcpu  # serial
    irq_affinity  26 $serialcpu  # i2c (NOR flash)
    irq_affinity  27 $diskcpu
    irq_affinity  28 $wificpu    # ath9k
    irq_affinity  29 $wificpu    # ath10k
    irq_affinity  94 $mocacpu
    irq_affinity  96 $eth0cpu    # *all* PFE
  fi
}

get_moca_iface() {
  # TODO(apenwarr): remove this once all devices use 'moca0' as the name.
  if [ -e /sys/class/net/moca0 ]; then
    # GFRG200
    echo "moca0"
  else
    # GFMS100/GFHD100/GFHD200
    echo "eth1"
  fi
}

get_wifi_macaddr_param() {
  local WIFI_MAC=$(hnvram -qr MAC_ADDR_WIFI)
  if [ -z "$WIFI_MAC" ]; then
    local MAC=$(hnvram -qr MAC_ADDR)
    if [ -z "${MAC}" ]; then
      echo "$0: WARNING: Box has no MAC_ADDR nor MAC_ADDR_WIFI" >&2
    else
      local WIFI_MAC=$(mac_addr_increment "${MAC}" 3)
    fi
  fi
  if [ -n "$WIFI_MAC" ]; then
    echo "wifi_addr=$WIFI_MAC"
  fi
}

load_wifi_drivers() {
  if [ "$(kernopt wifical)" != "1" ] && ! experiment SkipBluetoothWifi; then
    # wifical=1 means Litepoint Wifi/Bluetooth calibration. We cannot load
    # the production firmware at all, or it barfs.

    # WARNING: Keep ath9k first, ath10k second, other wifi stuff later so that
    # the wifi device ordering is constant.
    modprobe -a ath9k ath10k_pci mwl8k 2>/dev/null

    local mwifi_param=$(get_wifi_macaddr_param)
    modprobe mwifiex $mwifi_param 2>/dev/null

    modprobe -a mwifiex_sdio mwifiex_usb mwifiex_pcie ath9k_htc 2>/dev/null

    # On GFRG250, the Quantenna PCIe module has not initialized by the time the
    # kernel enumerates PCIe devices. For development, also include GFRG200 and
    # GFRG210.
    if contains ",GFRG200,GFRG210,GFRG250," ",$(cat /etc/platform),"; then
      echo 1 > /sys/bus/pci/rescan
    fi
    if contains ",GFHD254," ",$(cat /etc/platform)," && ! startswith "$(uname -r)" "3"; then
      echo 1 > /sys/bus/pci/devices/0000:01:00.0/remove
      sleep 1
      echo 1 > /sys/bus/pci/rescan
    fi
    modprobe qdpc-host 2>/dev/null

    # Enable wifi beacon and fatal debug logging.
    if [ -e /sys/module/ath9k/parameters/debug ]; then
      echo 0x00004400 >/sys/module/ath9k/parameters/debug
    fi

    # Enable pktgen.
    modprobe pktgen 2>/dev/null
  fi
}

create_br0() {
  echo -n "Creating bridge br0... "
  brctl setfd br0 1
  if is-ptp || experiment NoSTP; then
    brctl stp br0 off
  else
    brctl stp br0 on
  fi
  # Storage and network boxes should have higher precedence (lower value)
  #  when choosing the root of the spanning tree.
  #  The default in Linux is 0x8000.
  if is-network-box || is-windcharger; then
    brctl setbridgeprio br0 0x7980
  elif is-storage-box; then
    brctl setbridgeprio br0 0x7990
  fi
  for i in lan0 eth0; do
    [ -e /sys/class/net/$i/address ] && \
      ip link set br0 address \
      $(cat /sys/class/net/$i/address) && \
      break
  done
  echo "done"
  ip link set br0 up
  ip link set br0 up  # kernel 3.2.26 bug: fails the first time
}

create_br1() {
  echo -n "Creating bridge br1... "
  brctl setfd br1 1
  brctl stp br1 off
  echo "done"
  ip link set br1 up
  ip link set br1 up  # kernel 3.2.26 bug: fails the first time
}

configure_moca_vlan() {
  # VLAN tag for MoCA interface
  ip link add link $MOCAIFC name $MOCAIFC.0 type vlan id 0
  # SO_PRIORITY -> 802.1p mapping
  for prio in 0 1 2 3 4 5 6 7; do
    vconfig set_egress_map $MOCAIFC.0 $prio $prio >/dev/null 2>&1
  done
}

add_interfaces_to_bridge() {
  local bridge=$1
  shift

  echo "Adding interfaces to $bridge... "

  # Move any existing network info from added network interfaces to the bridge.
  # Only strictly needed for br0, when NFS booting; this prevents losing the
  # NFS connection in the middle of the script.
  for x in "$@"; do
    [ -e "$x" ] || continue

    local x="${x#/sys/class/net/}"
    echo "Adding interface $x..."

    ip link set $x up &&
    ip -o -f inet addr show $x |
    while read a b c ip d brd junk; do
      ip addr add $ip broadcast $brd dev $bridge
    done &&
    if [ -n "$MOCAIFC" ] && [ $x = $MOCAIFC ]; then
      x="${MOCAIFC}.0"
    fi &&
    brctl addif $bridge $x &&
    ip -o -f inet route list dev $x |
    while read dst routeinfo; do
      ip route add dev $bridge $dst $routeinfo metric 1 &&
      ip route del dev $x $dst $routeinfo
    done &&
    ip -o -f inet addr show $x |
    while read a b c ip junk; do
      ip addr del $ip dev $x
    done
  done
}

add_multicast_route() {
  echo "Adding RFC2365 multicast route..."
  ip route add 239.0.0.0/8 dev br0
  kernopt ip |
  while IFS=: read ip serverip gatewayip netmask hostname ifc junk; do
    # $ip should be already setup by simpleramfs.
    # TODO(apenwarr): simpleramfs should set up $gatewayip too.
    #  But right now its included networking commands are too limited.
    if [ -n "$gatewayip" ]; then
      echo "IP: adding default gateway '$gatewayip'"
      if experiment BigInitRwnd; then
        initrwnd='initrwnd 1000'
      else
        initrwnd=
      fi
      ip route add default via "$gatewayip" $initrwnd
    fi
  done
}

if_up_no_bridging() {
  local dev="$1"
  echo "Adding interface $dev..."
  ip link set "$dev" up &&
  kernopt ip |
  while IFS=: read ip serverip gatewayip netmask hostname ifc junk; do
    # $ip should be already setup by simpleramfs.
    # TODO(apenwarr): simpleramfs should set up $gatewayip too.
    #  But right now its included networking commands are too limited.
    if [ -n "$gatewayip" ]; then
      echo "IP: adding default gateway '$gatewayip'"
      ip route add default via "$gatewayip"
    fi
  done
  echo "Bringing up interface $dev..."
  ifup "$dev"
  enable_rx_coalescing "$dev"
}

bridge_up() {
  local br="$1"
  echo "Bringing up the bridge $br..."
  ip link set dev "$br" up  # kernel 3.16 behavior, sometimes br0 does not come up.
  ifup "$br"
}

enable_rx_coalescing() {
  # Enable receive interrupt coalescing, this is a significant
  # performance gain for multicast receive.
  ethtool -C $1 rx-usecs 1000
}

enable_hw_offloads() {
  # Enable hardware offloads, do this at the end to avoid
  # invalid access caused by powered down core
  ethtool -K $1 rx on tx on sg on gso on gro on
}

wan0_down() {
  if [ -e /sys/class/net/wan0 ]; then
    echo "Shutting down the WAN port..."
    ifdown wan0
    ifdown wan0.2
    ip link del wan0.2
  fi
}

bridge_down() {
  local br="$1"

  if [ -n /sys/class/net/$br/bridge ]; then
    ifdown $br
    echo "Shutting down $br interface"
  fi

  for x in $(ls /sys/class/net); do
    if [ -e /sys/class/net/$x/device ]; then
      brctl delif $br $x
    fi
  done
  ifdown $br
  [ -e /sys/class/net/$br ] && brctl delbr $br
}

# encode a string as base64
encode()
{
  python -c "import base64; print base64.b64encode('$1')"
}

add_firewall_rules_to_vlan_input() {
  if [ -e /sys/class/net/sw0.peer ]; then
    # open ports for peer to peer configuration control
    ip46tables -A vlan-input -i sw0.peer -p tcp --dport 80 -j ACCEPT
    ip46tables -A vlan-input -i sw0.peer -p tcp --dport 443 -j ACCEPT
    # Open ports used by macsecd for peer communication.
    ip46tables -A vlan-input -i sw0.peer -p tcp --dport 50000 -j ACCEPT
    ip46tables -A vlan-input -i sw0.peer -p tcp --dport 50001 -j ACCEPT
  fi
}

setup_tcp_congestion_control() {
  # load all algorithms we may want to use
  modprobe -a tcp_acubic tcp_turbulent tcp_transonic

  # make all "available" algorithms "allowed" so any process can use it
  cat /proc/sys/net/ipv4/tcp_available_congestion_control \
      >/proc/sys/net/ipv4/tcp_allowed_congestion_control

  # log
  echo -n "tcp_allowed_congestion_control="
  cat /proc/sys/net/ipv4/tcp_allowed_congestion_control
}

case "$1" in
  start)
    if is-fiberjack; then
      lan_port=$(cat /etc/config/lanport)
      echo "Starting LAN."
      ifup lo
      if [ -n "$lan_port" ]; then
        ifup $lan_port
      fi
      exit 0
    fi

    if is-moca-present; then
      MOCAIFC=$(get_moca_iface)
    fi

    if ! is-spacecast && ! is-ptp; then
      load_wifi_drivers
    fi

    setup_tcp_congestion_control

    echo 1 >/proc/sys/net/ipv4/conf/all/arp_ignore

    # Create a default resolv.conf and /etc/hosts in case DHCP doesn't.
    # (this is always true if using nfsroot).
    # 8.8.8.8 and 8.8.4.4 are well-known IPv4 Google public DNS servers
    # that can be used from anywhere, so it makes a good place to start.
    # 2001:4860:4860::8888 is one of the two IPv6 Google public DNS servers
    # (the other being 2001:4860:4860::8844).
    # DHCP will just overwrite this file when it's ready to go.
    if [ ! -r /etc/resolv.conf ]; then
      # Note: uClibc only reads the first three nameserver entries.
      (echo 'nameserver 8.8.8.8';
       echo 'nameserver 8.8.4.4';
       echo 'nameserver 2001:4860:4860::8888') >/etc/resolv.conf
    fi
    if [ ! -r /etc/hosts ]; then
      echo "127.0.0.1 localhost $(hostname)" >/etc/hosts
    fi

    # Configure dhclient with the right hostname etc.
    setup-dhclient

    # Configure loopback
    ifup lo

    # Quantenna-specific initialization.
    if interface_exists quantenna; then
      ip link set dev quantenna up
      i=0
      while true; do
        if ! interface_exists wlan"$i"; then
          echo "add wlan$i 3" >/sys/class/net/quantenna/vlan
          break
        fi
        i=$((i+1))
      done
    fi

    # GFRG2X0 sets lan0, wan0, and moca0 in pfe kernel module
    # from addrs passed on command line from barebox

    if is-ptp; then
      # set defaults
      ptp-config -d craft_ipaddr 192.168.1.1/24
      ptp-config -d vlan_inband 4091
      ptp-config -d vlan_ooband 50
      ptp-config -d vlan_peer 60
      # TODO(edjames) default should depend on hi/lo radio model
      ptp-config -d local_ipaddr 192.168.2.1/24
      ptp-config -d peer_ipaddr 192.168.2.2/24
      ptp-config -d password_admin $(encode admin)
      ptp-config -d password_guest $(encode guest)

      # mac addrs
      set_mac_from_hnvram craft0 MAC_ADDR
      set_mac_from_hnvram sw0 MAC_ADDR_WAN

      # craft0 (static ip for UI)
      ptp-config -i craft_ipaddr
      if_up_no_bridging craft0

      # bring up inband vlan (will dhcp)
      ptp-config -i vlan_inband
      # no if_up_no_bridging, inband is in br0

      # bring up ooband vlan (will dhcp)
      ptp-config -i vlan_ooband
      if_up_no_bridging sw0.ooband

      # bring up peer vlan, set our static ip
      ptp-config -i vlan_peer
      ptp-config -i local_ipaddr
      if_up_no_bridging sw0.peer

      add_firewall_rules_to_vlan_input
    fi

    if is-spacecast; then
      if_up_no_bridging lan0

      (nohup /sbin/lan0_watchdog 2>&1 > /dev/null) &
    else
      # br0 bridges Ethernet, MoCA, and WiFi AP interfaces
      create_br0

      # br1 only bridges captive portal WiFi interfaces
      create_br1

      if is-moca-present; then
        configure_moca_vlan
      fi

      add_interfaces_to_bridge br0 \
        /sys/class/net/[ea]th[0-9] \
        /sys/class/net/*.inband \
        /sys/class/net/lan[0-9] \
        /sys/class/net/moca[0-9] \
        /sys/class/net/wlan[0-9]

      add_multicast_route

      bridge_up br0
      bridge_up br1
      redistribute_irqs
    fi

    if is-moca-present; then
      echo "starting moca..."
      babysit 30 runmoca 2>&1 | logos mocad &
      if experiment MocaFlowControl; then
        moca-flowctl --enable
      fi
    fi

    if [ -e /sys/class/net/wan0 ]; then
      echo "Bringing up the WAN port..."
      ip link add link wan0 name wan0.2 type vlan id 2
      sysctl net.ipv4.conf.all.forwarding=1
      sysctl net.ipv6.conf.all.forwarding=1
      sysctl net.ipv6.conf.wan0.accept_ra=2
      sysctl net.ipv6.conf.wan0/2.accept_ra=2
      ifup wan0
      ifup wan0.2

      # set the class priority to 802.1p bit mapping.  We don't want
      # pbit=0,1 ever, so force into 2.
      vconfig set_egress_map wan0.2 0 2
      vconfig set_egress_map wan0.2 1 2
      for prio in 2 3 4 5 6 7; do
        vconfig set_egress_map wan0.2 $prio $prio >/dev/null 2>&1
      done

      # Enable receive interrupt coalescing, this is a significant
      # performance gain for multicast receive.
      enable_rx_coalescing wan0
    fi

    if is-tv-box || is-storage-box; then
      # Enable hardware offloads, do this at the end to avoid
      # invalid access caused by powered down core
      enable_hw_offloads eth0
      if is-moca-present; then
        enable_hw_offloads $MOCAIFC
      fi
    fi
    if is-tv-box && experiment TVBoxNoGigEthernet; then
      # advertise only 10 & 100 at half and full duplex.
      ethtool -s eth0 advertise 0xf
    fi
    if is-storage-box && experiment TcpWmem1Meg; then
      # Values from GFRG210, applied to all Storage Boxes.
      echo "4096 16384 1019904" >/proc/sys/net/ipv4/tcp_wmem
    fi
    if experiment AggressiveTCP; then
      # technically only needed for server
      echo 1 >/proc/sys/net/ipv4/tcp_thin_linear_timeouts
      echo 1 >/proc/sys/net/ipv4/tcp_thin_dupack
      if [ -e "/proc/sys/net/ipv4/tcp_min_rtt_wlen" ]; then
        # Only on more recent kernels
        echo 10 >/proc/sys/net/ipv4/tcp_min_rtt_wlen
        echo 4 >/proc/sys/net/ipv4/tcp_early_retrans
      fi

      # technically only needed for client
      echo 0 >/proc/sys/net/ipv4/tcp_moderate_rcvbuf
    fi
    if experiment BigSocketBuffers; then
      # technically only rbuf increase needed on receiver, and only wbuf
      # on sender, but for simplicity just do it everywhere for now.
      n=8000000
      echo $n $n $n | tee /proc/sys/net/ipv4/tcp_?mem >/dev/null
      echo $n | tee /proc/sys/net/core/?mem_* >/dev/null
    fi
    ;;
  stop)
    if [ ! -e /tmp/NFS ]; then
      if is-fiberjack; then
        lan_port=$(cat /etc/config/lanport)
        echo "Stopping LAN."
        ifdown lo
        if [ -n "$lan_port" ]; then
          ifdown $lan_port
        fi
        exit 0
      fi

      if is-spacecast; then
        ifdown lan0
        echo "Shutting down lan0 interface"
      else
        wan0_down

        bridge_down br0
        bridge_down br1
      fi

      # Kill mocad and mocactl
      if [ -e /dev/bmoca0 ]; then
        echo -n "mocacfg kill"
        pkillwait mocad
      fi
    fi
    exit 0
    ;;
  restart|reload)
    "$0" stop
    "$0" start
    ;;
  *)
    echo "Usage: $0 {start|stop|restart}"
    exit 1
esac
